package httputil.okhttpinterceptor;/*
 * Copyright 2018-2019 MarkLogic Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.ietf.jgss.*;


import javax.security.auth.login.LoginException;
import java.io.IOException;

import java.security.PrivilegedAction;
import java.util.*;

/**
 * An HTTP Request interceptor that modifies the request headers to enable
 * Kerberos authentication. It appends the Kerberos authentication token to the
 * 'Authorization' request header for Kerberos authentication
 *
 */
public class HTTPKerberosAuthInterceptor implements Interceptor {
	String host;
    public static  boolean bfirst = false;


  public HTTPKerberosAuthInterceptor(String host ) {

    this.host = host;


  }

  /**
   * This method builds the Authorization header for Kerberos. It
   * generates a request token based on the service ticket, client principal name and
   * time-stamp
   *
   * @param serverPrincipalName
   *            the name registered with the KDC of the service for which we
   *            need to authenticate
   * @return the HTTP Authorization header token
   */
  private String buildAuthorizationHeader(String serverPrincipalName) throws LoginException
  {

    final CreateAuthorizationHeaderAction action = new CreateAuthorizationHeaderAction(
      serverPrincipalName);



    /*
     * Subject.doAs takes in the Subject context and the action to be run as
     * arguments. This method executes the action as the Subject given in the
     * argument. We do this in order to provide the Subject's context so that we
     * reuse the service ticket which will be populated in the Subject rather
     * than getting the service ticket from the KDC for each request. The GSS
     * API populates the service ticket in the Subject and reuses it
     * 
     */

    //Subject.doAs(loginContext.getSubject(), action);
    action.run();
    return action.getNegotiateToken();
  }

  /**
   * Creates a privileged action which will be executed as the Subject using
   * Subject.doAs() method. We do this in order to create a context of the user
   * who has the service ticket and reuse this context for subsequent requests
   */
  private static class CreateAuthorizationHeaderAction implements PrivilegedAction {

    String serverPrincipalName;

    private StringBuffer outputToken = new StringBuffer();

    private CreateAuthorizationHeaderAction( final String serverPrincipalName) {

      this.serverPrincipalName = serverPrincipalName;
    }

    private String getNegotiateToken() {
      return outputToken.toString();
    }

    /*
     * Here GSS API takes care of getting the service ticket from the Subject
     * cache or by using the TGT information populated in the subject which is
     * done by buildSubjectCredentials method. The service ticket received is
     * populated in the subject's private credentials along with the TGT
     * information since we will be executing this method as the Subject. For
     * subsequent requests, the cached service ticket will be re-used. For this
     * to work the System property javax.security.auth.useSubjectCredsOnly must
     * be set to true.
     */
    @Override
    public Object run() {
      try {

        Oid SPNEGO_OID = new Oid("1.3.6.1.5.5.2");


        final GSSManager manager = GSSManager.getInstance();
        final GSSName serverName = manager.createName(serverPrincipalName, GSSName.NT_HOSTBASED_SERVICE);

        final GSSContext context = manager.createContext(serverName.canonicalize(SPNEGO_OID), SPNEGO_OID, null,
            GSSContext.DEFAULT_LIFETIME);
        context.requestMutualAuth(true);


        byte[] inToken = new byte[0];
        byte[] outToken = context.initSecContext(inToken, 0, inToken.length);

        outputToken.append(new String(Base64.getEncoder().encode(outToken)));
        context.dispose();
      } catch (GSSException exception) {
        throw new RuntimeException(exception.getMessage(), exception);
      }
      return null;
    }
  }

  /*
   * The server principal name which we pass as an argument to
   * buildAuthorizationHeader method would always start with 'HTTP/' because we
   * create the principal name for the Marklogic server starting with 'HTTP/'
   * followed by the host name as mentioned in the <a href=
   * "http://docs.marklogic.com/guide/security/external-auth#id_13835"> External
   * Security Guide</a>.
   */
  @Override
  public Response intercept(Chain chain) throws IOException {


    Request request = chain.request();


    String authValue  = "Negotiate";
    Request authenticatedRequest = null;
    Response response = chain.proceed(request);
    if (response.code() == 401) {
      response.close();
      try {
        authValue = "Negotiate " + buildAuthorizationHeader("HTTP@" + host);
      } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
      }
      authenticatedRequest = request.newBuilder().header("Authorization", authValue).build();
      return chain.proceed(authenticatedRequest);
    }
    return response;
  }
}